﻿// Copyright (c) 2010 2011 2012 唐锐(also tr0217)
// mailto:tr0217@163.com
// The earliest release time: 2010-12-18
// Last modification time:
// Accompanying files of necessity:
//        * 
//
// This file and the accompanying files of this project may be freely used provided the following
// conditions are met:
//        * This copyright statement is not removed or modified.
//        * The code is not sold in uncompiled form.  (Release as a compiled binary which is part
//          of an application is fine)
//        * The design, code, or compiled binaries are not "Re-branded".
//        
// Optional:
//        * Redistributions in binary form must reproduce the above copyright notice.
//        * I receive a fully licensed copy of the product (regardless of whether the product is
//          is free, shrinkwrap, or commercial).  This is optional, though if you release products
//          which use code I've contributed to, I would appreciate a fully licensed copy.
//
// In addition, you may not:
//        * Publicly release modified versions of the code or publicly release works derived from
//          the code without express written authorization.
//
// NO WARRANTY:
//        THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
//        ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
//        WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
//        IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
//        INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
//        NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
//        OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
//        WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
//        ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
//        OF SUCH DAMAGE.
//

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;
using System.Windows.Forms;
using System.Collections.ObjectModel;
using System.Drawing.Design;
using System.Windows.Forms.VisualStyles;

namespace TR0217.ControlEx
{
    [ToolboxBitmap(typeof(SplitButton), "SplitButton.bmp")]
    public class SplitButton : Button
    {
        [Browsable(true)]
        [Category("Action")]
        [Description("Occurs when the button part of the SplitButton is clicked by the mouse.")]
        public event MouseEventHandler ButtonMouseClick;

        [Browsable(true)]
        [Category("Action")]
        [Description("Occurs when the button part of the SplitButton is double clicked by the mouse.")]
        public event MouseEventHandler ButtonMouseDoubleClick;

        [Browsable(true)]
        [Category("Action")]
        [Description("Occurs when the split part of the SplitButton is clicked by the mouse.")]
        public event MouseEventHandler SplitMouseClick;

        [Browsable(true)]
        [Category("Action")]
        [Description("Occurs when the split part of the SplitButton is double clicked by the mouse.")]
        public event MouseEventHandler SplitMouseDoubleClick;

        private bool _enableSplitEffect = true;
        private int _splitWidth = 23;
        private Image _splitImage = null;

        private PushButtonState _splitPartState = PushButtonState.Normal;
        private PushButtonState _buttonPartState = PushButtonState.Normal;

        private Rectangle _buttonRect = new Rectangle(0,0,106,23);
        private Rectangle _splitRect = new Rectangle(106,0,23,23);

        public SplitButton()
            :base()
        {
            base.Size = new Size(129, 23);
            base.BackColor = Color.Transparent;
        }


        [Category("Appearance")]
        [Description("The image that will be displayed on split part of this control.")]
        public Image SplitImage
        {
            get { return _splitImage; }
            set { _splitImage = value; }
        }

        [Category("Appearance")]
        [Description("The width of the split part of this control in pixel.")]
        public int SplitWidth
        {
            get { return _splitWidth; }
            set 
            {
                if (value > 0)
                {
                    _splitWidth = value;
                    _buttonRect.Height = base.Height;
                    _buttonRect.Width = base.Width - _splitWidth;
                    _splitRect.Height = base.Height;
                    _splitRect.Width = _splitWidth;
                    _splitRect.X = base.Width - _splitWidth;
                }
            }
        }


        [Category("Behavior")]
        [Description("Indicates whether it behave like a SplitButton or DropDownButton. "
            +"When this property is false, it behave just like a ToolStripDropDownButton")]
        [DefaultValue(true)]
        public bool EnableSplitEffect
        {
            get { return _enableSplitEffect; }
            set { _enableSplitEffect = value; }
        }

        protected override void OnMouseDown(MouseEventArgs mevent)
        {
            base.OnMouseDown(mevent);

            if (_splitRect.Contains(mevent.Location))
                _splitPartState = PushButtonState.Pressed;
            else
                _buttonPartState = PushButtonState.Pressed;
        }
        

        protected override void OnMouseUp(MouseEventArgs mevent)
        {
            base.OnMouseUp(mevent);
            _buttonPartState = _splitPartState = PushButtonState.Hot;
            this.Invalidate();
        }

        protected override void OnMouseEnter(EventArgs e)
        {
            base.OnMouseEnter(e);
            _buttonPartState = _splitPartState = PushButtonState.Hot;
        }


        protected override void OnMouseLeave(EventArgs e)
        {
            base.OnMouseLeave(e);
            if(this.Focused)
                _buttonPartState = _splitPartState = PushButtonState.Default;
            else
                _buttonPartState = _splitPartState = PushButtonState.Normal;
        }

        protected override void OnLostFocus(EventArgs e)
        {
            base.OnLostFocus(e);
            _buttonPartState = _splitPartState = PushButtonState.Normal;
        }

        protected override void OnEnabledChanged(EventArgs e)
        {
            base.OnEnabledChanged(e);
            if(base.Enabled == false)
                _buttonPartState = _splitPartState = PushButtonState.Disabled;
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            _buttonRect.Height = base.Height;
            _buttonRect.Width = base.Width - _splitWidth;
            _splitRect.Height = base.Height;
            _splitRect.Width = _splitWidth;
            _splitRect.X = base.Width - _splitWidth;
            _hotPart = null;
            _normalPart = null;
            _pressedPart = null;
            _disabledPart = null;
            _defaultPart = null;
            _backgroundImage = null;
        }

        protected override void OnBackColorChanged(EventArgs e)
        {
            base.OnBackColorChanged(e);
            _backgroundImage = null;
        }

        protected override void OnBackgroundImageLayoutChanged(EventArgs e)
        {
            base.OnBackgroundImageLayoutChanged(e);
            _backgroundImage = null;
        }

        protected override void OnBackgroundImageChanged(EventArgs e)
        {
            base.OnBackgroundImageChanged(e);
            _backgroundImage = null;
        }


        protected override void OnMouseClick(MouseEventArgs e)
        {
            base.OnMouseClick(e);
            if (_splitRect.Contains(e.Location))
            {
                if (SplitMouseClick != null)
                    SplitMouseClick(this, e);
            }
            else
            {
                if (ButtonMouseClick != null)
                    ButtonMouseClick(this, e);
            }
        }

        protected override void OnMouseDoubleClick(MouseEventArgs e)
        {
            base.OnMouseDoubleClick(e);
            if (_splitRect.Contains(e.Location))
            {
                if (SplitMouseDoubleClick != null)
                    SplitMouseDoubleClick(this, e);
            }
            else
            {
                if (ButtonMouseDoubleClick != null)
                    ButtonMouseDoubleClick(this, e);
            }
        }


        private Image _hotPart = null;
        private Image _normalPart = null;
        private Image _pressedPart = null;
        private Image _disabledPart = null;
        private Image _defaultPart = null;
        private Image _backgroundImage = null;
        private const int _partWidth = 4;

        protected override void OnPaint(PaintEventArgs pevent)
        {
            ButtonRenderer.DrawParentBackground(pevent.Graphics,base.ClientRectangle, this);
            if (!_enableSplitEffect)
                _splitPartState = _buttonPartState = _buttonPartState == PushButtonState.Pressed?_buttonPartState:_splitPartState;

            this.PaintPart(pevent.Graphics, _splitRect, _splitPartState, 0);
            this.PaintPart(pevent.Graphics, _buttonRect, _buttonPartState, 1);

        }

        private void PaintPart(Graphics graphics, Rectangle _splitRect, PushButtonState _splitPartState, int what)
        {
            int x = _splitRect.X;
            int y = _splitRect.Y;

            Image img = this.PrepareImage(_splitPartState, what);
            Pen pen;
            Rectangle rect = new Rectangle(_splitRect.X + 4, _splitRect.Y + 4, _splitRect.Width - 9, _splitRect.Height - 9);
            if (what == 1)
            {
                Color textColor = base.Enabled ? this.ForeColor : ControlPaint.Dark(this.BackColor);
                TextFormatFlags flags = TextFormatFlags.PreserveGraphicsTranslateTransform | TextFormatFlags.PreserveGraphicsClipping | TextFormatFlags.TextBoxControl | TextFormatFlags.WordBreak;
                if (!this.ShowKeyboardCues)
                {
                    flags |= TextFormatFlags.HidePrefix;
                }
                if (this.RightToLeft == RightToLeft.Yes)
                {
                    flags |= TextFormatFlags.RightToLeft | TextFormatFlags.Right;
                }

                if (_backgroundImage == null)
                    _backgroundImage = this.BuildBackgroundImage(rect);

                ButtonRenderer.DrawButton(graphics, _splitRect, _splitPartState);
                graphics.DrawImage(_backgroundImage, rect);
                if(base.Image != null)
                    graphics.DrawImage(base.Image,this.CreatePaintRect(rect,base.Image.Width,base.Image.Height,base.ImageAlign));

                SizeF sizef = graphics.MeasureString(base.Text, base.Font);
                int width = (int)sizef.Width + 1;
                int height = (int)sizef.Height + 1;
                Rectangle rectStr = this.CreatePaintRect(rect, width, height, base.TextAlign);
                RectangleF rectf = new RectangleF(rectStr.X,rectStr.Y,rectStr.Width,rectStr.Height);
                graphics.DrawString(base.Text,base.Font,new SolidBrush(textColor),rectf);

                x = _splitRect.Right - _partWidth;

                pen = new Pen(new SolidBrush(Color.DarkGray));
            }
            else
            {
                pen = new Pen(new SolidBrush(Color.LightGray));

                if (_splitImage == null)
                    ButtonRenderer.DrawButton(graphics, _splitRect, false, _splitPartState);
                else
                    ButtonRenderer.DrawButton(
                        graphics, 
                        _splitRect, 
                        _splitImage,
                        this.CreatePaintRect(rect, _splitImage.Width, _splitImage.Height, System.Drawing.ContentAlignment.MiddleCenter), 
                        false,
                        _splitPartState);
            }

            graphics.DrawImage(img, new Rectangle(x,y+1,_partWidth,base.Height-2),
                new Rectangle(0, 1, _partWidth, base.Height - 2), GraphicsUnit.Pixel);

            graphics.DrawLine(pen, base.Width - _splitWidth - what, 4, base.Width - _splitWidth - what, base.Height - 5);
            if (base.Focused && what == 1)
            {
                Pen focusedPen = new Pen(Color.Black, 1);
                focusedPen.DashStyle = DashStyle.Dot;
                focusedPen.Alignment = PenAlignment.Inset;
                graphics.DrawRectangle(focusedPen, rect);
                focusedPen.Dispose();
            }
            pen.Dispose();
        }

        private System.Drawing.Image BuildBackgroundImage(Rectangle rect)
        {
            Bitmap bitmap = new Bitmap(rect.Width, rect.Height);
            Graphics graphics = Graphics.FromImage(bitmap);
            Brush brush = new SolidBrush(base.BackColor);
            Rectangle paintRect = new Rectangle(0, 0, rect.Width, rect.Height);
            graphics.FillRectangle(brush, paintRect);
            brush.Dispose();
            if (base.BackgroundImage != null)
            {
                Rectangle rectResult = this.CreatePaintRect(paintRect,
                    base.BackgroundImage.Width,
                    base.BackgroundImage.Height,
                    base.BackgroundImageLayout);
                if (base.BackgroundImageLayout == ImageLayout.Tile)
                {
                    brush = new TextureBrush(base.BackgroundImage);
                    graphics.FillRectangle(brush,rectResult);
                    brush.Dispose();
                }
                else
                    graphics.DrawImage(base.BackgroundImage, rectResult);

            }

            return bitmap;
        }

        private Rectangle CreatePaintRect(Rectangle rect, int width, int height, ImageLayout imageLayout)
        {
            Rectangle rectResult = new Rectangle();
            rectResult.X = rect.X;
            rectResult.Y = rect.Y;
            rectResult.Width = width;
            rectResult.Height = height;


            switch (imageLayout)
            {
                case ImageLayout.None:
                    break;
                case ImageLayout.Center:
                    rectResult.X = rect.X + (rect.Width - rectResult.Width) / 2;
                    rectResult.Y = rect.Y + (rect.Height - rectResult.Height) / 2;
                    break;
                case ImageLayout.Stretch:
                    rectResult.Width = rect.Width;
                    rectResult.Height = rect.Height;
                    break;
                case ImageLayout.Tile:
                    rectResult.Width = rect.Width;
                    rectResult.Height = rect.Height;
                    break;
                case ImageLayout.Zoom:
                    rectResult.Height = rect.Height;
                    rectResult.Width = rect.Height;
                    rectResult.X = rect.X + (rect.Width - rectResult.Width) / 2;
                    rectResult.Y = rect.Y + (rect.Height - rectResult.Height) / 2;
                    break;
            }

            return rectResult;
        }

        private Rectangle CreatePaintRect(Rectangle rect,int width, int height, System.Drawing.ContentAlignment contentAlignment)
        {
            Rectangle rectResult = new Rectangle();
            rectResult.X = rect.X;
            rectResult.Y = rect.Y;
            rectResult.Width = width > rect.Width ? rect.Width : width;
            rectResult.Height = height > rect.Height ? rect.Height : height;

            switch (contentAlignment)
            {
                case System.Drawing.ContentAlignment.BottomCenter:
                    rectResult.Y = rect.Y + rect.Height - rectResult.Height;
                    rectResult.X = rect.X + (rect.Width - rectResult.Width) / 2;
                    break;
                case System.Drawing.ContentAlignment.BottomLeft:
                    rectResult.X = rect.X;
                    rectResult.Y = rect.Y + rect.Height - rectResult.Height;
                    break;
                case System.Drawing.ContentAlignment.BottomRight:
                    rectResult.Y = rect.Y + rect.Height - rectResult.Height;
                    rectResult.X = rect.X + rect.Width - rectResult.Width;
                    break;
                case System.Drawing.ContentAlignment.MiddleCenter:
                    rectResult.X = rect.X + (rect.Width - rectResult.Width) / 2;
                    rectResult.Y = rect.Y + (rect.Height - rectResult.Height) / 2;
                    break;
                case System.Drawing.ContentAlignment.MiddleLeft:
                    rectResult.X = rect.X;
                    rectResult.Y = rect.Y + (rect.Height - rectResult.Height) / 2;
                    break;
                case System.Drawing.ContentAlignment.MiddleRight:
                    rectResult.Y = rect.Y + (rect.Height - rectResult.Height) / 2;
                    rectResult.X = rect.X + rect.Width - rectResult.Width;
                    break;
                case System.Drawing.ContentAlignment.TopCenter:
                    rectResult.X = rect.X + (rect.Width - rectResult.Width) / 2;
                    rectResult.Y = rect.Y;
                    break;
                case System.Drawing.ContentAlignment.TopLeft:
                    break;
                case System.Drawing.ContentAlignment.TopRight:
                    rectResult.Y = rect.Y;
                    rectResult.X = rect.X + rect.Width - rectResult.Width;
                    break;
                default:
                    break;
            }
            return rectResult;
        }

        private Image PrepareImage(PushButtonState _splitPartState, int what)
        {
            switch (_splitPartState)
            {
                case PushButtonState.Normal:
                    if (_normalPart == null)
                    {
                        Bitmap bitmap = this.BuildResourceBitMap(_splitPartState);
                        _normalPart = new Bitmap(_partWidth, base.Height);
                        this.BuildDestBitmap(_normalPart, bitmap);
                    }
                    return _normalPart; 
                case PushButtonState.Hot:
                    if (_hotPart == null)
                    {
                        Bitmap bitmap = this.BuildResourceBitMap(_splitPartState);
                        _hotPart = new Bitmap(_partWidth, base.Height);
                        this.BuildDestBitmap(_hotPart, bitmap);
                    }
                    return _hotPart;
                case PushButtonState.Pressed:
                    if (_pressedPart == null)
                    {
                        Bitmap bitmap = this.BuildResourceBitMap(_splitPartState);
                        _pressedPart = new Bitmap(_partWidth, base.Height);
                        this.BuildDestBitmap(_pressedPart, bitmap);
                    }
                    return _pressedPart;
                case PushButtonState.Disabled:
                    if (_disabledPart == null)
                    {
                        Bitmap bitmap = this.BuildResourceBitMap(_splitPartState);
                        _disabledPart = new Bitmap(_partWidth, base.Height);
                        this.BuildDestBitmap(_disabledPart, bitmap);
                    }
                    return _disabledPart;
                default:
                    if (_defaultPart == null)
                    {
                        Bitmap bitmap = this.BuildResourceBitMap(_splitPartState);
                        _defaultPart = new Bitmap(_partWidth, base.Height);
                        this.BuildDestBitmap(_defaultPart, bitmap);
                    }
                    return _defaultPart;

            }
        }

        private void BuildDestBitmap(Image _normalPart,Bitmap bitmap)
        {
            Graphics g = Graphics.FromImage(_normalPart);
            g.DrawImage(bitmap, new Rectangle(0, 0, _partWidth, base.Height), new Rectangle(_partWidth, 0, _partWidth, base.Height), GraphicsUnit.Pixel);           
        }

        private Bitmap BuildResourceBitMap(PushButtonState _splitPartState)
        {
            Bitmap bitmap = new Bitmap(_partWidth*3, base.Height);
            Graphics g = Graphics.FromImage(bitmap);
            Rectangle rect = new Rectangle(0,0,bitmap.Width,base.Height);
            ButtonRenderer.DrawButton(g, rect, false, _splitPartState);
            g.Dispose();
            return bitmap;
        }
    }
}

